/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.seppiko.pigeon.configuration;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.Security;
import java.util.Objects;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.conscrypt.Conscrypt;
import org.seppiko.pigeon.exceptions.PigeonCheckException;
import org.seppiko.pigeon.models.MailConfigEntity;

/**
 * @author Leonard Woo
 */
@Slf4j
public class PigeonConfiguration {

  private static final PigeonConfiguration INSTANCE = new PigeonConfiguration();

  public static PigeonConfiguration getInstance() {
    return INSTANCE;
  }

  private PigeonConfiguration() {
    init();
  }

  @Getter
  private JdbcEntity jdbcConfig;

  @Getter
  private MailConfigEntity mailConfig;

  @Getter
  private String salt;

  @Getter
  private String iv;

  @Getter
  private boolean mailSpy;

  @Getter
  private boolean onlyAdd;

  @Getter
  private int hideAdd;

  private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());

  private void init() {
    try {
      // Load config file
      String filepath = System.getProperty(Environment.PIGEON_CONFIG_FILE, Environment.PIGEON_DEFAULT_FILE_NAME_YAML);
      InputStream is = getPath(filepath);
      if (is == null) {
        is = getPath(Environment.PIGEON_DEFAULT_FILENAME_YML);
      }
      if(is == null) {
        throw new FileNotFoundException("pigeon.yaml not found");
      }
      BufferedReader reader = loadReader( is );

      // Config ObjectMapper with YAML
      yamlMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
      yamlMapper.configure(DeserializationFeature. FAIL_ON_UNKNOWN_PROPERTIES, false);

      // Parser config file
      JsonNode root = yamlMapper.readTree(reader).get("pigeon");
      log.info("Pigeon Config: " + root.toString());
      loadConfig(root);

      // DNS cache ttl
      System.setProperty("networkaddress.cache.ttl", "10");
      System.setProperty("networkaddress.cache.negative.ttl", "10");

      // Register Google Conscrypt
      Security.addProvider(Conscrypt.newProvider());

    } catch (Throwable t) {
      log.error("", t);
    }
  }

  private void loadConfig(JsonNode root) throws PigeonCheckException {
    JsonNode jdbc = root.get("jdbc");
    this.jdbcConfig = yamlMapper.convertValue(jdbc, JdbcEntity.class);

    JsonNode mail = root.get("mail");
    this.mailConfig = yamlMapper.convertValue(mail, MailConfigEntity.class);

    JsonNode security = root.get("security");
    this.salt = security.get("salt").asText();
    this.iv = security.get("iv").asText();

    JsonNode mailSpy = root.get("mailSpy");
    this.mailSpy = mailSpy.get("enable").asBoolean(false);
    this.onlyAdd = mailSpy.get("onlyAdd").asBoolean(true);
    this.hideAdd = mailSpy.get("hideAdd").asInt(3);
    if (this.hideAdd < 0 || this.hideAdd > 3) {
      throw new PigeonCheckException("hideAdd must be between 0 and 3");
    }
  }

  private BufferedReader loadReader(InputStream is) {
    return new BufferedReader( new InputStreamReader( new BufferedInputStream( is ) ) );
  }

  private InputStream getPath(String name) {
    try {
      ClassLoader cl = this.getClass().getClassLoader();
      InputStream is = cl.getResourceAsStream(name);
      if (is == null) {
        try {
          is = new FileInputStream(
              Objects.requireNonNull(cl.getResource(name)).getPath());
        } catch (NullPointerException ex) {
          is = null;
        }
      }
      if (is == null) {
        is = new FileInputStream(name);
      }
      return is;
    } catch (IOException ex) {
      log.warn(ex.getMessage());
      return null;
    }
  }

}
